﻿using System;
using System.IO;
using System.Linq;
using System.Text;

namespace EncodingConverter
{
    /// <summary>
    /// Console application used to convert all the files inside a folder that follow a given pattern, to a given encoding. 
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            //args = new [] {"C:\\Files", "*.csv", "ANSI"};

            #region Get the list of available encodings. You can find a list on MSDN at -> http://msdn.microsoft.com/en-us/library/system.text.encoding.aspx

            var availableEncodings = Encoding.GetEncodings();

            #endregion

            #region Informations

            if (args == null || args.Length == 0)
            {
                Console.WriteLine("INFORMATION: You can use this console application to change the encoding of a text file. When running the application you have to specify the following 3 arguments separated by space:");
                Console.WriteLine(string.Empty);
                Console.WriteLine("* The path to the folder that contains the files to be converted. Example: C:\\Files");
                Console.WriteLine(string.Empty);
                Console.WriteLine("* A file pattern (see the next link for details about the available wildcard characters http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/find_c_search_where.mspx?mfr=true). Example: *.csv");
                Console.WriteLine(string.Empty);
                Console.WriteLine("* The desired encoding. Example: ANSI");
                Console.WriteLine(string.Empty);
                Console.WriteLine(string.Empty);
                Console.WriteLine("For example, let's suppose that you're calling the application passing the arguments: C:\\Files *.csv ANSI");
                Console.WriteLine("In this case, the application will convert to an ANSI encoding all the files with the extension .csv that are located under the folder C:\\Files");
                Console.WriteLine(string.Empty);
                Console.WriteLine("For a complete list of supported encodings call this console application passing the argument: HELP");
                return;
            }

            #endregion

            #region Help

            if (string.Compare(args[0], "help", StringComparison.CurrentCultureIgnoreCase) == 0)
            {
                Console.WriteLine("For the 3rd argument you can pass one of the following codes:");
                Console.WriteLine("-----------------------------------------------------------------");
                Console.WriteLine("Code Page   -   Name   -   Description");
                Console.WriteLine("-----------------------------------------------------------------");
                Console.WriteLine("{0}   -   {1}   -   {2}", "ANSI", "ANSI", "Because there in no fixed ANSI encoding, we will consider that ANSI is the Windows-1252 Encoding. That's because, usually, \"ANSI Encoding\" means \"the default code page for the current system\" which generally is Windows-1252.");
                foreach (var availableEncoding in availableEncodings)
                {
                    Console.WriteLine("{0}   -   {1}   -   {2}", availableEncoding.CodePage, availableEncoding.Name, availableEncoding.DisplayName);
                }
                return;
            }

            #endregion            

            #region Validation

            if (string.IsNullOrEmpty(args[0]) || string.IsNullOrEmpty(args[0].Trim()))
            {
                Console.WriteLine("ERROR: Missing the first argument - please specify the path to the folder that contains the files to convert. If you are running the application from the console make sure you're providing as the first argument the path, as the second argument the file pattern and as the third argument the desired encoding; something like this: C:\\>EncodingConverter.exe C:\\Files *.csv ANSI");
                return;
            }

            if (!Directory.Exists(args[0]))
            {
                Console.WriteLine("ERROR: Can't find the directory: {0}", args[0]);
                return;
            }

            if (args.Length < 2 || string.IsNullOrEmpty(args[1]) || string.IsNullOrEmpty(args[1].Trim()))
            {
                Console.WriteLine("ERROR: Missing the second argument - please specify the file pattern using wildcard characters (http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/find_c_search_where.mspx?mfr=true). If you are running the application from the console make sure you're providing as the first argument the path, as the second argument the file pattern and as the third argument the desired encoding; something like this: C:\\>EncodingConverter.exe C:\\Files *.csv ANSI");
                return;
            }
            
            if (args.Length < 3 || string.IsNullOrEmpty(args[2]))
            {
                Console.WriteLine("ERROR: Missing the third argument - please specify the desired encoding. For example ANSI. For a complete list of available encodings call this application with the argument HELP");
                return;                
            }

            Encoding desiredEncoding = null;
            if (string.Compare(args[2], "ANSI", StringComparison.CurrentCultureIgnoreCase) == 0)
            {
                desiredEncoding = Encoding.GetEncoding(1252);
            }
            else
            {
                var enc = availableEncodings.FirstOrDefault(e => string.Compare(e.Name, args[2], StringComparison.CurrentCultureIgnoreCase) == 0);
                if (enc != null)
                {
                    desiredEncoding = Encoding.GetEncoding(enc.CodePage);
                }
                else
                {
                    #region Check if the user passed a code page number

                    int code;
                    if (Int32.TryParse(args[2].Trim(), out code))
                    {
                        var info = availableEncodings.FirstOrDefault(e => e.CodePage == code);
                        if (info != null)
                            desiredEncoding = Encoding.GetEncoding(code);
                    }

                    #endregion
                }
            }
            if (desiredEncoding == null)
            {
                Console.WriteLine("ERROR: Invalid encoding: {0}. For a complete list of available encodings call this application with the argument HELP", args[2]);
                return;
            }

            #endregion

            var files = Directory.GetFiles(args[0], args[1], SearchOption.AllDirectories);
            if (files.Length == 0)
            {
                Console.WriteLine("The folder {0} contains no files that match the file pattern: {1}. No file was converted.", args[0], args[1]);
                return;
            }
            
            Console.WriteLine("There are {0} files to process, please wait.", files.Length);
            const int len = 1024;
            var remainingNumberOfFilesToProcess = files.Length;
            foreach (var file in files)
            {
                var converted = false;
                Encoding initialEncoding;
                using (var sr = new StreamReader(file))
                {
                    initialEncoding = sr.CurrentEncoding;                    

                    if (Equals(initialEncoding, desiredEncoding))
                    {
                        Console.WriteLine("[ ] {0} is already in {1} (code: {2}) format so it was not converted. Number of remaining files to convert: {3}.", file, desiredEncoding.EncodingName, desiredEncoding.CodePage, --remainingNumberOfFilesToProcess);                       
                    }
                    else
                    {
                        try
                        {
                            using (var convertor = new StreamWriter(file.Remove(file.LastIndexOf('.')) + ".ectmp", false, desiredEncoding))
                            {
                                var noOfCharactersRead = -1;
                                while (sr.Peek() >= 0 && noOfCharactersRead != 0)
                                {
                                    var originalText = new char[len];
                                    noOfCharactersRead = sr.Read(originalText, 0, originalText.Length);

                                    if (noOfCharactersRead > 0)
                                    {
                                        var encBytes = initialEncoding.GetBytes(originalText.Take(noOfCharactersRead).ToArray());
                                        var desiredEncodingBytes = Encoding.Convert(initialEncoding, desiredEncoding, encBytes);
                                        var desiredEncodingText = desiredEncoding.GetString(desiredEncodingBytes);
                                        convertor.Write(desiredEncodingText);
                                    }
                                }
                            }

                            converted = true;
                        }
                        catch (Exception exp)
                        {
                            Console.WriteLine("[ ] {0} conversion error {1}.  Number of remaining files to convert: {2}.", file, exp.Message, --remainingNumberOfFilesToProcess);
                            Console.WriteLine(exp.StackTrace);
                            if (exp.InnerException != null)
                            {
                                Console.WriteLine(exp.InnerException.Message);
                            }
                            Console.WriteLine();
                        }
                    }

                    sr.Close();
                }

                if (converted)
                {
                    File.Delete(file);
                    File.Move(file.Remove(file.LastIndexOf('.')) + ".ectmp", file);
                    Console.WriteLine("[x] {0} was converted from {1} (code: {2}) to {3} (code: {4}). Number of remaining files to convert: {5}.", file, initialEncoding.EncodingName, initialEncoding.CodePage, desiredEncoding.EncodingName, desiredEncoding.CodePage, --remainingNumberOfFilesToProcess);
                }
                else
                {
                    File.Delete(file.Remove(file.LastIndexOf('.')) + ".ectmp");
                }
            }
        }
    }
}
